﻿// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// 
// Copyright (c) 2014, Dr. Masroor Ehsan
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//    * Neither the name of the <organization> nor the
//      names of its contributors may be used to endorse or promote products
//      derived from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
// $Id$
// 
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Common.Logging;
using Sgml;

namespace dseScraper
{
    internal class SgmlQuotesParser
    {
        private readonly ILog _log;
        private readonly QuoteFieldColumnMap _map;

        public SgmlQuotesParser()
        {
            _map = new QuoteFieldColumnMap();
            _log = LogManager.GetLogger(GetType());

            //#  Trading Code  LTP*  High  Low  Close Price  YCP  Change  Trade  Volume            
            _map.AddSearchPattern(StockQuoteField.Symbol, @"\bcode\b");
            _map.AddSearchPattern(StockQuoteField.High, @"\bhigh\b");
            _map.AddSearchPattern(StockQuoteField.Low, @"\blow\b");
            _map.AddSearchPattern(StockQuoteField.Close, @"\bclosep\b");
            _map.AddSearchPattern(StockQuoteField.Ltp, @"\bltp\b");
            _map.AddSearchPattern(StockQuoteField.PrevClose, @"\bycp\b");
            _map.AddSearchPattern(StockQuoteField.Change, @"\bchange\b");
            _map.AddSearchPattern(StockQuoteField.Trades, @"\btrade\b");
            _map.AddSearchPattern(StockQuoteField.Volume, @"\bvolume\b");
        }

        public List<StockQuote> Execute(string html, DateTime utcTime)
        {
            var quotes = new List<StockQuote>();
            try
            {
                using (var sgml = new SgmlReader
                                  {
                                      DocType = "HTML",
                                      CaseFolding = CaseFolding.ToLower,
                                      InputStream = new StringReader(html)
                                  })
                {
                    var xdoc = XDocument.Load(sgml);
                    var tableRows = xdoc.Descendants("table").Descendants("tr").ToList();
                    var tableHeaderProcessed = false;
                    foreach (var rowElem in tableRows)
                    {
                        if (!tableHeaderProcessed)
                        {
                            ProcessTableHeader(rowElem.Descendants("td").ToList());
                            tableHeaderProcessed = true;
                            continue;
                        }

                        ProcessTableRow(rowElem.Descendants("td").ToList(), quotes, utcTime);
                    }
                }
            }
            catch (Exception exc)
            {
                _log.Error(exc);
            }
            return quotes;
        }

        private void ProcessTableHeader(IEnumerable<XElement> columns)
        {
            var index = 0;
            foreach (var column in columns)
            {
                DiscoverFieldIndices(column.Value.Trim(), index);
                index++;
            }
        }

        private void ProcessTableRow(IList<XElement> columns, List<StockQuote> quotes, DateTime utcTime)
        {
            var discoveredFields = _map.GetPriceFields();
            var price = new StockQuote {DateTime = DateTime.SpecifyKind(utcTime, DateTimeKind.Utc)};
            foreach (var field in discoveredFields)
            {
                var index = _map.GetColumnIndex(field);
                var data = columns[index].Value.Trim();
                price.SetData(field, data);
            }

            quotes.Add(price);
        }

        private bool DiscoverFieldIndices(string rawData, int index)
        {
            foreach (var pattern in _map.GetSearchPatterns())
            {
                if (Regex.IsMatch(rawData, pattern, RegexOptions.IgnoreCase))
                {
                    _map.SetColumnIndex(_map.PriceFieldForsearchPattern(pattern), index);
                    return true;
                }
            }

            return false;
        }
    }
}